package com.gitee.wsl.time.format

import com.gitee.wsl.text.reader.MicroStrReader
import com.gitee.wsl.ext.bitcode.umod
import com.gitee.wsl.ext.map.increment
import com.gitee.wsl.time.TimeSpan
import com.gitee.wsl.time.local.KlockLocale
import com.gitee.wsl.time.local.TimezoneNames
import kotlinx.datetime.LocalDateTime
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.minutes


//
//data class PatternLocalDateTimeFormat @JvmOverloads constructor(
//    val format: String,
//    val locale: KlockLocale? = null,
//    val tzNames: TimezoneNames = TimezoneNames.DEFAULT,
//    val options: Options = Options.DEFAULT
//) : LocalDateTimeFormat {
//    companion object {
//        @Suppress("MayBeConstant", "unused")
//        private const val serialVersionUID = 1L
//    }
//
//    val realLocale get() = locale ?: KlockLocale.default
//
//
//    data class Options(val optionalSupport: Boolean = false) {
//        companion object {
//            @Suppress("MayBeConstant", "unused")
//            private const val serialVersionUID = 1L
//
//            val DEFAULT = Options(optionalSupport = false)
//            val WITH_OPTIONAL = Options(optionalSupport = true)
//        }
//    }
//
//    fun withLocale(locale: KlockLocale?) = this.copy(locale = locale)
//    fun withTimezoneNames(tzNames: TimezoneNames) = this.copy(tzNames = this.tzNames + tzNames)
//    fun withOptions(options: Options) = this.copy(options = options)
//    fun withOptional() = this.copy(options = options.copy(optionalSupport = true))
//    fun withNonOptional() = this.copy(options = options.copy(optionalSupport = false))
//
//    private val openOffsets = LinkedHashMap<Int, Int>()
//    private val closeOffsets = LinkedHashMap<Int, Int>()
//
//    internal val chunks = arrayListOf<String>().also { chunks ->
//        val s = MicroStrReader(format)
//        while (s.hasMore) {
//            if (s.peekChar() == '\'') {
//                val escapedChunk = s.readChunk {
//                    s.tryRead('\'')
//                    while (s.hasMore && s.readChar() != '\'') Unit
//                }
//                chunks.add(escapedChunk)
//                continue
//            }
//            if (options.optionalSupport) {
//                val offset = chunks.size
//                if (s.tryRead('[')) {
//                    openOffsets.increment(offset)
//                    continue
//                }
//                if (s.tryRead(']')) {
//                    closeOffsets.increment(offset - 1)
//                    continue
//                }
//            }
//            chunks.add(s.tryReadOrNull("do") ?: s.readRepeatedChar())
//        }
//    }.toList()
//
//    internal val regexChunks: List<String> = chunks.map {
//        when (it) {
//            "E", "EE", "EEE", "EEEE", "EEEEE", "EEEEEE" -> """(\w+)"""
//            "z", "zzz" -> """([\w\s\-\+:]+)"""
//            "do" -> """(\d{1,2}\w+)"""
//            "d" -> """(\d{1,2})"""
//            "dd" -> """(\d{2})"""
//            "M" -> """(\d{1,5})"""
//            "MM" -> """(\d{2})"""
//            "MMM", "MMMM", "MMMMM" -> """(\w+)"""
//            "y" -> """(\d{1,5})"""
//            "yy" -> """(\d{2})"""
//            "yyy" -> """(\d{3})"""
//            "yyyy" -> """(\d{4})"""
//            "YYYY" -> """(\d{4})"""
//            "H", "k" -> """(\d{1,2})"""
//            "HH", "kk" -> """(\d{2})"""
//            "h", "K" -> """(\d{1,2})"""
//            "hh", "KK" -> """(\d{2})"""
//            "m" -> """(\d{1,2})"""
//            "mm" -> """(\d{2})"""
//            "s" -> """(\d{1,2})"""
//            "ss" -> """(\d{2})"""
//            "S" -> """(\d{1,9})"""
//            "SS" -> """(\d{2})"""
//            "SSS" -> """(\d{3})"""
//            "SSSS" -> """(\d{4})"""
//            "SSSSS" -> """(\d{5})"""
//            "SSSSSS" -> """(\d{6})"""
//            "SSSSSSS" -> """(\d{7})"""
//            "SSSSSSSS" -> """(\d{8})"""
//            "SSSSSSSSS" -> """(\d{9})"""
//            "X", "XX", "XXX", "x", "xx", "xxx", "Z" -> """([\w:\+\-]+)"""
//            "a" -> """(\w+)"""
//            " " -> """(\s+)"""
//            else -> when {
//                it.startsWith('\'') -> "(" + Regex.escape(it.substring(1, it.length - 2)) + ")"
//                else -> "(" + Regex.escape(it) + ")"
//            }
//        }
//    }
//
//      //**
//     //* @return the regular expression string used for matching this format, able to be composed into another regex
//     //*
//    fun matchingRegexString(): String = regexChunks.mapIndexed { index, it ->
//        if (options.optionalSupport) {
//            val opens = openOffsets.getOrElse(index) { 0 }
//            val closes = closeOffsets.getOrElse(index) { 0 }
//            buildString {
//                repeat(opens) { append("(?:") }
//                append(it)
//                repeat(closes) { append(")?") }
//            }
//        } else {
//            it
//        }
//    }.joinToString("")
//
//    //val escapedFormat = Regex.escape(format)
//    internal val rx2: Regex = Regex("^" + matchingRegexString() + "$")
//
//
//    // EEE, dd MMM yyyy HH:mm:ss z -- > Sun, 06 Nov 1994 08:49:37 GMT
//    // YYYY-MM-dd HH:mm:ss
//
//    override fun format(dd: LocalDateTime): String {
//        val utc = dd
//        var out = ""
//        for (name in chunks) {
//            val nlen = name.length
//            out += when (name) {
//                "E", "EE", "EEE" -> utc.dayOfWeek.localShortName(realLocale)
//                "EEEE", "EEEEE", "EEEEEE" -> utc.dayOfWeek.localName(realLocale)
//                "z", "zzz" -> dd.offset.timeZone
//                "d", "dd" -> utc.dayOfMonth.padded(nlen)
//                "do" -> realLocale.getOrdinalByDay(utc.dayOfMonth)
//                "M", "MM" -> utc.month1.padded(nlen)
//                "MMM" -> Month[utc.month1].localName(realLocale).substr(0, 3)
//                "MMMM" -> Month[utc.month1].localName(realLocale)
//                "MMMMM" -> Month[utc.month1].localName(realLocale).substr(0, 1)
//                "y" -> utc.year
//                "yy" -> (utc.year % 100).padded(2)
//                "yyy" -> (utc.year % 1000).padded(3)
//                "yyyy" -> utc.year.padded(4)
//                "YYYY" -> utc.year.padded(4)
//
//                "H", "HH" -> mconvertRangeZero(utc.hour, 24).padded(nlen)
//                "k", "kk" -> mconvertRangeNonZero(utc.hour, 24).padded(nlen)
//
//                "h", "hh" -> mconvertRangeNonZero(utc.hour, 12).padded(nlen)
//                "K", "KK" -> mconvertRangeZero(utc.hour, 12).padded(nlen)
//
//                "m", "mm" -> utc.minute.padded(nlen)
//                "s", "ss" -> utc.second.padded(nlen)
//
//                "S", "SS", "SSS", "SSSS", "SSSSS", "SSSSSS", "SSSSSSS", "SSSSSSSS", "SSSSSSSSS" -> {
//                    val milli = utc.millisecond
//                    val base10length = log10(utc.millisecond.toDouble()).toInt() + 1
//                    if (base10length > name.length) {
//                        (milli.toDouble() * 10.0.pow(-1 * (base10length - name.length))).toInt()
//                    } else {
//                        "${milli.padded(3)}000000".substringUseLength(0, name.length)
//                    }
//                }
//                "X", "XX", "XXX", "x", "xx", "xxx" -> {
//                    when {
//                        name.startsWith("X") && dd.offset.totalMinutesInt == 0 -> "Z"
//                        else -> {
//                            val p = if (dd.offset.totalMinutesInt >= 0) "+" else "-"
//                            val hours = (dd.offset.totalMinutesInt / 60).absoluteValue
//                            val minutes = (dd.offset.totalMinutesInt % 60).absoluteValue
//                            when (name) {
//                                "X", "x" -> "$p${hours.padded(2)}"
//                                "XX", "xx" -> "$p${hours.padded(2)}${minutes.padded(2)}"
//                                "XXX", "xxx" -> "$p${hours.padded(2)}:${minutes.padded(2)}"
//                                else -> name
//                            }
//                        }
//                    }
//                }
//                "a" -> realLocale.h12Marker[if (utc.hour < 12) 0 else 1]
//                else -> when {
//                    name.startsWith('\'') -> name.substringUseLength(1, name.length - 1)
//                    else -> name
//                }
//            }
//        }
//        return out
//    }
//
//    override fun tryParse(str: String, doThrow: Boolean, doAdjust: Boolean): LocalDateTime? {
//        var millisecond = 0
//        var second = 0
//        var minute = 0
//        var hour = 0
//        var day = 1
//        var month = 1
//        var fullYear = 1970
//        var offset: TimeSpan? = null
//        var isPm = false
//        var is12HourFormat = false
//        val result = rx2.find(str) ?: return null //println("Parser error: Not match, $str, $rx2");
//        for ((name, value) in chunks.zip(result.groupValues.drop(1))) {
//            if (value.isEmpty()) continue
//
//            when (name) {
//                "E", "EE", "EEE", "EEEE", "EEEEE", "EEEEEE" -> Unit // day of week (Sun | Sunday)
//                "z", "zzz" -> { // timezone (GMT)
//                    offset = MicroStrReader(value).readTimeZoneOffset(tzNames)
//                }
//                "d", "dd" -> day = value.toInt()
//                "do" -> day = realLocale.getDayByOrdinal(value)
//                "M", "MM" -> month = value.toInt()
//                "MMM" -> month = realLocale.monthsShort.indexOf(value) + 1
//                "y", "yyyy", "YYYY" -> fullYear = value.toInt()
//                "yy" -> if (doThrow) throw RuntimeException("Not guessing years from two digits.") else return null
//                "yyy" -> fullYear = value.toInt() + if (value.toInt() < 800) 2000 else 1000 // guessing year...
//                "H", "HH", "k", "kk" -> hour = value.toInt()
//                "h", "hh", "K", "KK" -> {
//                    hour = value.toInt()
//                    is12HourFormat = true
//                }
//                "m", "mm" -> minute = value.toInt()
//                "s", "ss" -> second = value.toInt()
//                "S", "SS", "SSS", "SSSS", "SSSSS", "SSSSSS", "SSSSSSS", "SSSSSSSS", "SSSSSSSSS" -> {
//                    val base10length = log10(value.toDouble()).toInt() + 1
//                    millisecond = if (base10length > 3) {
//                        // only precision to millisecond supported, ignore the rest. ex: 9999999 => 999"
//                        (value.toDouble() * 10.0.pow(-1 * (base10length - 3))).toInt()
//                    } else {
//                        value.toInt()
//                    }
//                }
//                "X", "XX", "XXX", "x", "xx", "xxx" -> {
//                    when {
//                        name.startsWith("X") && value.first() == 'Z' -> offset = 0.hours
//                        name.startsWith("x") && value.first() == 'Z' -> {
//                            if (doThrow) throw RuntimeException("Zulu Time Zone is only accepted with X-XXX formats.") else return null
//                        }
//                        value.first() != 'Z' -> {
//                            val valueUnsigned = value.replace(":", "").removePrefix("-").removePrefix("+")
//                            val hours = when (name.length) {
//                                1 -> valueUnsigned.toInt()
//                                else -> valueUnsigned.take(2).toInt()
//                            }
//                            val minutes = when (name.length) {
//                                1 -> 0
//                                else -> valueUnsigned.drop(2).toInt()
//                            }
//                            offset = hours.hours + minutes.minutes
//                            if (value.first() == '-') {
//                                offset = -offset
//                            }
//                        }
//                    }
//                }
//                "MMMM" -> month = realLocale.months.indexOf(value) + 1
//                "MMMMM" -> if (doThrow) throw RuntimeException("Not possible to get the month from one letter.") else return null
//                "a" -> isPm = value.equals("pm", ignoreCase = true)
//                else -> {
//                    // ...
//                }
//            }
//        }
//        //return DateTime.createClamped(fullYear, month, day, hour, minute, second)
//        if (is12HourFormat) {
//            if (isPm) {
//                if (hour != 12) {
//                    hour += 12
//                }
//            } else {
//                if (hour == 12) {
//                    hour = 0
//                }
//            }
//        }
//        if (!doAdjust) {
//            if (month !in 1..12) if (doThrow) error("Invalid month $month") else return null
//            if (day !in 1..32) if (doThrow) error("Invalid day $day") else return null
//            if (hour !in 0..24) if (doThrow) error("Invalid hour $hour") else return null
//            if (minute !in 0..59) if (doThrow) error("Invalid minute $minute") else return null
//            if (second !in 0..59) if (doThrow) error("Invalid second $second") else return null
//            if (millisecond !in 0..999) if (doThrow) error("Invalid millisecond $millisecond") else return null
//        }
//        val dateTime = LocalDateTime(fullYear, month, day, hour umod 24, minute, second, millisecond)
//        return dateTime.toOffsetUnadjusted(offset ?: 0.hours)
//    }
//
//    override fun toString(): String = format
//}

private fun mconvertRangeZero(value: Int, size: Int): Int {
    return (value umod size)
}

private fun mconvertRangeNonZero(value: Int, size: Int): Int {
    val res = (value umod size)
    return if (res == 0) size else res
}

private fun MicroStrReader.readRepeatedChar(): String {
    return readChunk {
        val c = readChar()
        while (hasMore && (tryRead(c))) Unit
    }
}